-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local class = require 'mc.class'
local log = require 'mc.logging'
local skynet = require 'skynet'
local defs = require 'domain.defs'
local port = require 'domain.device.switch.chip.port'
local vlan = require 'domain.device.switch.chip.vlan'
local alarm = require 'alarm.alarm'
local ext_vlan = require 'domain.device.switch.chip.external_vlan'
local debug = require 'domain.debug.debug'
local monitor = require 'domain.debug.monitor'
local _, lsw_drv = pcall(require, 'lsw_drv')
local chip = class()

local mtr = monitor.get_instance()
function chip:ctor(db)
    self.component = {
        ports = {},
        vlans = {}
    }
    self.obj = {}
    self.db = db
    self.fixCnt = 0
    self.extVlan = ext_vlan.new(self, db)
    self.record_power_state = 1
end

function chip:add_object(obj)
    self.obj = obj
end

function chip:get_type()
    return self.obj.Type
end

function chip:update_type(type)
    if type == self.obj.Type then
        return
    end
 
    self.obj.Type = type
    local mgr = debug.get_instance()
    mgr:update_lsw_type(type)
end

function chip:add_port(obj)
    if self.component.ports[obj.PhysicalId] then
        return
    end
    self.component.ports[obj.PhysicalId] = port.new(obj, self)
end

function chip:add_vlan(obj)
    if self.component.vlans[obj.VlanId] then
        return
    end
    self.component.vlans[obj.VlanId] = vlan.new(obj, self)
end

function chip:hard_reset()
    
    local ok, err
    for _ = 1, 100 do
        ok, err = pcall(function()
            -- 复位芯片：0（下电） 1（上电）
            self.obj.ChipRst = 0
            skynet.sleep(10)
            self.obj.ChipRst = 1
        end)
        if ok then
            log:notice("[lsw]hard reset switch success")
            mtr:update_state(defs.NetConfigIdx.ChipHardReset, true)
            return
        end
        skynet.sleep(100)
    end
    log:error(string.format('[lsw]hard reset switch fail, obj %s, err %s', self.obj, err))
    mtr:update_state(defs.NetConfigIdx.ChipHardReset, false)
end

function chip:soft_reset()
    local res = lsw_drv.l_lsw_switch_soft_reset(self.obj.Type, self.obj.Unit)
    mtr:update_state(defs.NetConfigIdx.ChipSoftReset, res)
    if not res then
        error("lsw soft reset fail")
    end
    log:notice("lsw soft reset success")
end

function chip:chip_init()
    local res = lsw_drv.l_chip_init(self.obj.Type, self.obj.Unit, 0)
    mtr:update_state(defs.NetConfigIdx.ChipInit, res)
    if not res then
        error("lsw chip init fail")
    end
    log:notice("lsw chip init success")
end

local GE_SWITCHOEM_BUTT<const> = 0xff
function chip:update_oem_type()
    if self:is_power_off() then
        return true
    end
    local res = lsw_drv.l_get_oem_type_by_unit(self.obj.Unit)
    if res then
        self:update_type(res)
        log:debug("lsw chip get oem(%s) success", res)
        return true
    else
        self:update_type(GE_SWITCHOEM_BUTT)
        log:error("lsw chip get oem type fail")
        return false
    end
end

function chip:ports_init()
    for _, v in pairs(self.component.ports) do
        v:start_init()
    end
end

function chip:vlan_config()
    for _, v in pairs(self.component.ports) do
        v:port_vlan_init()
    end

    -- 配置外网vlan
    self.extVlan:start_init()
end

function chip:vlans_init()
    local res = lsw_drv.l_lsw_vlan_init(self.obj.Type, self.obj.Unit)
    mtr:update_state(defs.NetConfigIdx.VlanInit, res)
    if not res then
        log:error("lsw vlan init fail")
    else
        log:notice("lsw vlan init success")
    end
    for _, v in pairs(self.component.vlans) do
        v:start_init()
    end
end

local SWCHIPFIX_MAXTIMES<const> = 6
function chip:try_trigger_alarm()
    self.fixCnt = self.fixCnt + 1
    if self.fixCnt >= SWCHIPFIX_MAXTIMES and self.obj.HealthState == 0 then
        log:error("Rtl8367 unit=%u fix cnt reach threshold, set alarm(curcnt=%u, threshold=%u)",
            self.obj.Unit, self.fixCnt, SWCHIPFIX_MAXTIMES)
        self.obj.HealthState = 1
        -- 产生告警
        local alarm_instance = alarm.new()
        alarm_instance:event(defs.ALARM_TYPE.CHIP_ERR, self.obj.Unit, self.obj.SlotNumber, true)
    end
end

function chip:try_remove_alarm()
    self.fixCnt = 0
    if self.obj.HealthState == 1 then
        log:error("Rtl8367 unit=%u fix cnt reach threshold, clear alarm(curcnt=%u, threshold=%u)",
            self.obj.Unit, self.fixCnt, SWCHIPFIX_MAXTIMES)
        self.obj.HealthState = 0
        -- 消除告警
        local alarm_instance = alarm.new()
        alarm_instance:event(defs.ALARM_TYPE.CHIP_ERR, self.obj.Unit, self.obj.SlotNumber, false)
    end
end

function chip:start_init()
    -- 下电不初始化
    if self:is_power_off() then
        return
    end
    self:hard_reset()
    skynet.sleep(20)
    self:chip_init()
    self:ports_init()
    -- vlan初始化
    self:vlans_init()
    -- 配置芯片端口vlan
    self:vlan_config()
    self:soft_reset()
end

function chip:handle_powerstate_event()
    if self.record_power_state == self.obj.PowerState then
        return
    end

    log:notice('[lsw] lsw chip power state(%s) changed', self.obj.PowerState)
    self.record_power_state = self.obj.PowerState
    local alarm_instance = alarm.new()
    alarm_instance:event(
        defs.ALARM_TYPE.LSW_POWER_OFF, self.obj.Unit, self.obj.SlotNumber, self.record_power_state == 0)
end

function chip:is_power_off()
    return self.obj.PowerState == 0
end

function chip:healthy_check()
    self:handle_powerstate_event()
    -- 下电状态不检测
    if self:is_power_off() then
        log:info("[lsw] chip is power off.")
        self:try_remove_alarm()
        return true
    end
    for _, v in pairs(self.component.ports) do
        if not v:is_health() then
            log:debug("[lsw] switch chip is not healthy")
            return false
        end
    end
    self:try_remove_alarm()
    return true
end

function chip:update_port_info()
    -- 下电状态不检测
    if self:is_power_off() then
        return
    end
    for _, v in pairs(self.component.ports) do
        v:update_port_info()
    end
end

function chip:port_connect_phy(phys)
    for _, v in pairs(self.component.ports) do
        local phy = phys:get_phy_by_id(v:get_prop('ConPhyAddr'))
        if phy then
            v:set_prop('connect_phy', phy)
        end
    end
end

local RETRY_TIMES<const> = 3
function chip:update_errcode()
    local err_code = mtr:get_config_errcode()
    local ok, res
    for _ = 1, RETRY_TIMES do
        ok, res = pcall(function()
            self.obj.ErrorCode = err_code
        end)
        if ok then
            return
        end
        skynet.sleep(10)
    end
    log:error('[lsw] update configure error code failed, errcode: %s, error: %s', err_code, res)
end

return chip
